package logic;

import gui.MapGUI;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.UnreadableException;

import java.awt.Point;
import java.util.LinkedList;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import org.apache.commons.collections.buffer.PriorityBuffer;

public class Map extends Agent {

	/**
	 * 
	 */
	private static final long serialVersionUID = -6069554372345811336L;
	private int width, height;
	private MapField[][] mapFields;
	private final int crossCost = 100;
	private final int straightCost = 11;
	MapGUI gui;

	@Override
	protected void setup() {
		super.setup();
		Object[] args = getArguments();
		int width = Integer.parseInt((String) args[0]);
		int height = Integer.parseInt((String) args[1]);
		this.width = width + 2;
		this.height = height + 2;
		mapFields = new MapField[this.getWidth()][this.getHeight()];
		createFields();
		buildWalls1();

		addBehaviour(new OneShotBehaviour(this) {

			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			@Override
			public void action() {

				SwingUtilities.invokeLater(new Runnable() {

					@Override
					public void run() {
						try {
							JFrame frame = new JFrame("czesc");
							frame
									.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
							gui = new MapGUI((Map) myAgent);
							frame.add(gui);
							frame.pack();
							frame.setVisible(true);
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}

					}
				});
			}
		});

		addBehaviour(new Behaviour(this) {

			@Override
			public boolean done() {
				return false;
			}

			@Override
			public void action() {
				ACLMessage msg = receive();
				if (msg != null) {
					try {
						Point a = (Point) msg.getContentObject();
						// System.out.println(a);
						mapFields[a.x][a.y].setType(FieldType.AGENT);
						gui.repaint();

					} catch (UnreadableException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				} else
					block();
			}
		});

	}

	private void buildWalls() {
		// Surroundings
		for (int i = 0; i < this.getWidth(); i++) {
			for (int j = 0; j < this.getHeight(); j++) {
				if (i == 0 || j == 0 || i == getWidth() - 1
						|| (j == getHeight() - 1)) {
					mapFields[i][j].setType(FieldType.WALL);
				}
			}

		}

		for (int k = getWidth() / 15; k < getWidth() * 3 / 4; k++) {
			mapFields[k][getHeight() / 5].setType(FieldType.WALL);
			mapFields[k][getHeight() / 2].setType(FieldType.WALL);
		}

		for (int k = getHeight() / 5; k < getHeight() / 2; k++) {
			mapFields[getWidth() * 3 / 4 - 1][k].setType(FieldType.WALL);
		}

	}

	private void buildWalls1() {

		// Surroundings
		for (int i = 0; i < this.getWidth(); i++) {
			for (int j = 0; j < this.getHeight(); j++) {
				if (i == 0 || j == 0 || i == getWidth() - 1
						|| (j == getHeight() - 1)) {
					mapFields[i][j].setType(FieldType.WALL);
				}
			}

		}

		for (int k = getWidth() / 10; k < getWidth() * 9 / 10; k++) {
			mapFields[k][getHeight() / 2].setType(FieldType.WALL);
		}

		for (int k = getHeight() / 10; k < getHeight() * 9 / 10; k++) {
			mapFields[getWidth() / 2 - 1][k].setType(FieldType.WALL);
		}
	}

	private void createFields() {
		for (int i = 0; i < this.getWidth(); i++) {
			for (int j = 0; j < this.getHeight(); j++) {
				mapFields[i][j] = new MapField(i, j);
				mapFields[i][j].setType(FieldType.WAY);
			}

		}
	}

	public double fromTo(int x1, int y1, int x2, int y2) {
		return straightLineDistance(mapFields[x1][y1], mapFields[x2][y2]);
	}

	private double straightLineDistance(MapField start, MapField end) {
		double xx = end.getPositionX() - start.getPositionX();
		double yy = end.getPositionY() - start.getPositionY();
		double quad = yy * yy + xx * xx;
		return Math.sqrt(quad);
	}

	private void checkNeighbours(MapField centerField,
			MapField finalDestinationField, PriorityBuffer openList,
			PriorityBuffer closedList) {
		MapField currentlyCheckedField = null;
		final int x = centerField.getPositionX();
		final int y = centerField.getPositionY();

		currentlyCheckedField = mapFields[x + 1][y + 1];
		updateFieldDistances(centerField, finalDestinationField, openList,
				closedList, currentlyCheckedField, crossCost);

		currentlyCheckedField = mapFields[x - 1][y - 1];
		updateFieldDistances(centerField, finalDestinationField, openList,
				closedList, currentlyCheckedField, crossCost);

		currentlyCheckedField = mapFields[x + 1][y - 1];
		updateFieldDistances(centerField, finalDestinationField, openList,
				closedList, currentlyCheckedField, crossCost);

		currentlyCheckedField = mapFields[x - 1][y + 1];
		updateFieldDistances(centerField, finalDestinationField, openList,
				closedList, currentlyCheckedField, crossCost);

		currentlyCheckedField = mapFields[x][y + 1];
		updateFieldDistances(centerField, finalDestinationField, openList,
				closedList, currentlyCheckedField, straightCost);

		currentlyCheckedField = mapFields[x][y - 1];
		updateFieldDistances(centerField, finalDestinationField, openList,
				closedList, currentlyCheckedField, straightCost);

		currentlyCheckedField = mapFields[x + 1][y];
		updateFieldDistances(centerField, finalDestinationField, openList,
				closedList, currentlyCheckedField, straightCost);

		currentlyCheckedField = mapFields[x - 1][y];
		updateFieldDistances(centerField, finalDestinationField, openList,
				closedList, currentlyCheckedField, straightCost);

	}

	public static double heuristicDistance(MapField from, MapField to) {
		int xx = Math.abs(from.getPositionX() - to.getPositionX());
		int yy = Math.abs(from.getPositionY() - to.getPositionY());
		return Math.sqrt((xx * xx + yy * yy)) * 10;
		// return (xx + yy) * 10;
	}

	private void updateFieldDistances(MapField centerField,
			MapField finalDestinationField, PriorityBuffer openList,
			PriorityBuffer closedList, MapField currentlyCheckedField, int cost) {

		if (currentlyCheckedField.getType() == FieldType.WALL)
			return;

		if (!closedList.contains(currentlyCheckedField)) {
			if (!openList.contains(currentlyCheckedField)) {
				currentlyCheckedField.setFather(centerField);
				currentlyCheckedField.setG(cost + centerField.getG());
				currentlyCheckedField.setH(heuristicDistance(
						currentlyCheckedField, finalDestinationField));
				currentlyCheckedField.computeF();
				openList.add(currentlyCheckedField);

			} else {
				// New way is better than the previously computed
				if (currentlyCheckedField.getG() > cost + centerField.getG()) {
					openList.remove(currentlyCheckedField);
					currentlyCheckedField.setG(cost + centerField.getG());
					currentlyCheckedField.computeF();
					currentlyCheckedField.setFather(centerField);
					// Collections.sort(openList);
					openList.add(currentlyCheckedField);

				}
			}
		}
	}

	public LinkedList<Point> getShortestPath(int x1, int y1, int x2, int y2)
			throws Exception {

		PriorityBuffer openList = new PriorityBuffer(true);
		PriorityBuffer closedList = new PriorityBuffer(true);
		MapField startField = mapFields[x1][y1];
		MapField endField = mapFields[x2][y2];
		startField.setFather(null);
		endField.setFather(null);
		MapField Q = null;

		// startField.setType(FieldType.PATH);
		// endField.setType(FieldType.PATH);

		startField.setG(0);
		startField.setH(heuristicDistance(startField, endField));
		startField.computeF();

		openList.add(startField);

		while (true) {
			Q = (MapField) openList.remove();
			closedList.add(Q);
			checkNeighbours(Q, endField, openList, closedList);

			if (Q == endField)
				break;
			if (openList.isEmpty())
				throw new Exception("Path couldn be found!");

		}
		LinkedList<Point> path = new LinkedList<Point>();
		MapField it = endField;
		while (it.getFather() != null) {
			path.add(new Point(it.getPositionX(), it.getPositionY()));
			it = it.getFather();
			System.out.println(it.getPositionX() + " " + it.getPositionY());
		}
		return path;
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public FieldType getFiledType(int x, int y) {
		return mapFields[x][y].getType();
	}

	public static void main(String[] args) {
		Map m = new Map();
	}
}
